<?php
// 版本1.2
namespace core;

use InvalidArgumentException;
use PDOException;
use PDOStatement;
use RuntimeException;
use PDO;

class Model
{
    // 静态属性用于共享数据库连接
    protected static $db;
    protected static $transactionLevel = 0;
    // // 数据库连接实例
    // protected $db;

    // 表名
    protected $table;
    protected $name;

    // 主键（默认id）
    protected $pk = 'id';

    /**  @var array 允许的查询条件类型（防止SQL注入）*/
    protected $allowedOperators = ['=', '>', '<', '>=', '<=', '!=', 'LIKE', 'IN'];

    // 添加的字段
    protected $append = [];

    // 隐藏的字段
    protected $hidden = [];

    // 关联查询配置存储
    private $relationBuilders = [];

    // 预加载的关联列表
    private $with = [];

    /** @var array 存储当前查询的链式调用参数 */
    private $queryParams = [
        'conditions' => [],
        'order' => null,
        'limitOffset' => null,
        'limitCount' => null
    ];

    public function __construct()
    {
        // 共享数据库连接
        if (self::$db === null) {
            $config = $this->loadConfig();
            $this->initDbConnection($config);
        }

        // 设置表名
        $this->setTableName();
        // // 从配置中获取数据信息（建议通过容器注入配置，此处简化）
        // $config = $this->loadConfig();
        // // 初始化PDO连接
        // $this->initDbConnection($config);
        // // 自动生成或设置表名（支持手动覆盖）
        // $this->setTableName();
    }

    /**
     * 加载数据库配置（示例：从文件读取，实际建议通过容器注入）
     * @throws \RuntimeException
     * @return array
     */
    protected function loadConfig()
    {
        $configPath = __DIR__ . '/../app/config.php';
        if (!file_exists($configPath)) {
            throw new RuntimeException("数据库配置不存在：{$configPath}");
        }

        return require $configPath;
    }

    /**
     * 初始化PDO连接（分离连接逻辑，便于测试）
     * @param array $config
     * @throws \RuntimeException
     * @return void
     */
    protected function initDbConnection(array $config): void
    {
        try {
            $dsn = "mysql:host={$config['db_host']};dbname={$config['db_name']};charset=utf8mb4";
            $this->db = new PDO(
                $dsn,
                $config['db_user'],
                $config['db_pass'],
                [
                    PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
                    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
                    PDO::ATTR_EMULATE_PREPARES => false
                ]
            );
        } catch (PDOException $e) {
            throw new RuntimeException("数据库连接失败：{$e->getMessage()}", 0, $e);
        }
    }

    /**
     * 设置数据表名（手动指定优先）
     * @param mixed $tableName
     * @throws \InvalidArgumentException
     * @return void
     */
    protected function setTableName(): void
    {
        $tableName = $this->name;
        if ($tableName) {
            // 手动指定表名时，仅允许字母、数字、下划线
            if (!preg_match('/^[\w]+$/', $tableName)) {
                throw new InvalidArgumentException("表名包含非法字符，仅允许字母、数字、下划线");
            }
            $this->table = $tableName;
            return;
        }

        // 自动生成表名 （类名转小写下划线 + 可选前缀）
        $className = basename(str_replace('\\', '/', get_class($this)));
        $this->table = $this->camelizeToSnake($className);
    }

    /**
     * 驼峰命名转下划线命名（修复原正则问题）
     * @param string $str
     * @return string
     */
    protected function camelizeToSnake(string $str): string
    {
        return strtolower(preg_replace('/(?<=\w)(?=[A-Z])/', '_', $str));
    }

    /**
     * 构建查询条件 (链式调用)
     * @param array $conditions
     * @return self
     */
    public function where(array $conditions): self
    {
        $this->queryParams['conditions'] = array_merge(
            $this->queryParams['conditions'],
            $conditions
        );
        return $this;
    }

    /**
     * 设置排序 (链式调用)
     * @param string $order
     * @param string $direction
     * @return self
     */
    public function order(string $order, string $direction = 'ASC'): self
    {
        if (!preg_match('/^[\w]+$/', $order)) {
            throw new InvalidArgumentException("排序字段包含非法字符：{$order}");
        }
        $direction = strtoupper($direction) === 'DESC' ? 'DESC' : 'ASC';
        $this->queryParams['order'] = "`{$order}` {$direction}";
        return $this;
    }

    /**
     * 设置分页 (链式调用)
     * @param int $offset
     * @param int $count
     * @return self
     */
    public function limit(int $offset, int $count): self
    {
        $this->queryParams['limitOffset'] = max(0, $offset);
        $this->queryParams['limitCount'] = max(1, $count);

        return $this;
    }

    /**
     * 获取单条记录
     * @param mixed $conditions
     * @return array|null
     */
    public function find($conditions = null)
    {
        try {
            $this->applyConditions($conditions);
            $whereClause = $this->buildWhereClause();

            $sql = "SELECT * FROM `{$this->table}`{$whereClause['sql']} LIMIT 1";
            $stmt = $this->query($sql, $whereClause['params']);

            return $stmt->fetch() ?: null;

        } finally {
            $this->resetQueryParams();
        }
    }

    /**
     * 获取多条记录
     * @return array
     */
    public function select(): array
    {
        try {
            $whereClause = $this->buildWhereClause();

            $sql = "SELECT * FROM `{$this->table}`{$whereClause['sql']}";

            // 添加排序
            if ($this->queryParams['order']) {
                $sql .= " ORDER BY {$this->queryParams['order']}";
            }

            // 添加分页
            if ($this->queryParams['limitCount'] !== null) {
                $offset = $this->queryParams['limitOffset'] ?? 0;
                $limit = $this->queryParams['limitCount'];
                $sql .= " LIMIT {$offset}, {$limit}";
            }

            $stmt = $this->query($sql, $whereClause['params']);

            $result = $stmt->fetchAll();

            // 处理添加字段和隐藏字段
            $result = $this->processResult($result);

            return $result;
        } finally {
            $this->resetQueryParams();
        }
    }

    /**
     * 构建WHERE子句
     * @return array [sql, params]
     */
    private function buildWhereClause(): array
    {
        if (empty($this->queryParams['conditions'])) {
            return ['sql' => '', 'params' => []];
        }

        return $this->parseConditions($this->queryParams['conditions']);
    }

    /**
     * 解析查询条件
     * @param array $conditions
     * @return array
     */
    protected function parseConditions(array $conditions): array
    {
        $sqlParts = [];
        $params = [];

        foreach ($conditions as $key => $value) {
            $operator = '=';
            $field = $key;
            if(is_string($value)){
                $value = explode(',', $value);
            }

            if (strpos($key, ' ') !== false) {
                list($field, $operator) = explode(' ', $key, 2);
                if (!in_array(strtoupper($operator), $this->allowedOperators)) {
                    throw new InvalidArgumentException("不支持的操作符：{$operator}");
                }
            }

            if (!preg_match('/^[\w]+$/', $field)) {
                throw new InvalidArgumentException("字段名包含非法字符：{$field}");
            }

            // 处理NULL值
            if ($value === null) {
                $sqlParts[] = "{$field} IS NULL";
                continue;
            }

            // 处理IN操作符
            if (strtoupper($operator) === 'IN' && is_array($value)) {
                $placeholders = [];
                foreach ($value as $i => $val) {
                    $ph = ":{$field}_in_{$i}";
                    $placeholders[] = $ph;
                    $params[$ph] = $val;
                }
                $sqlParts[] = "`{$field}` IN (" . implode(', ', $placeholders) . ")";
            } else {
                $placeholder = ":{$field}";
                $sqlParts[] = "`{$field}` {$operator} {$placeholder}";
                $params[$placeholder] = $value;
            }
        }

        return [
            'sql' => ' WHERE ' . implode(' AND ', $sqlParts),
            'params' => $params
        ];
    }

    /**
     * 重置查询参数
     */
    private function resetQueryParams()
    {
        $this->queryParams = [
            'conditions' => [],
            'order' => null,
            'limitOffset' => null,
            'limitCount' => null
        ];
        $this->with = [];
        $this->relationBuilders = [];
    }

    /**
     * 应用查询条件
     * @param mixed $conditions
     */
    private function applyConditions($conditions)
    {
        if ($conditions === null)
            return;

        if (is_numeric($conditions)) {
            $this->queryParams['conditions'] = [$this->pk => $conditions];
        } elseif (is_array($conditions)) {
            $this->queryParams['conditions'] = $conditions;
        }
    }

    /**
     * 定义一对一关联（当前模型外键在关联模型）
     * @param string $model 关联模型类名
     * @param string $foreignKey 关联模型中的外键字段
     * @param string $localKey 当前模型的主键字段（默认id）
     * @return array
     */
    protected function belongsTo(string $model, string $foreignKey, string $localKey = 'id'): array
    {
        return [
            'type' => 'belongsTo',
            'model' => new $model,
            'foreignKey' => $foreignKey,
            'localKey' => $localKey,
        ];
    }

    /**
     * 定义一对多关联（当前模型主键在关联模型）
     * @param string $model 关联模型类名
     * @param string $foreignKey 关联模型中的外键字段
     * @param string $localKey 当前模型的主键字段（默认id）
     * @return self
     */
    protected function hasMany(string $model, string $foreignKey, string $localKey = 'id'): self
    {
        $this->relationBuilders[] = [
            'type' => 'hasMany',
            'model' => $model,
            'foreignKey' => $foreignKey,
            'localKey' => $localKey,
            'data' => []
        ];
        return $this;
    }

    /**
     * 预加载关联数据
     * @param mixed $relations 关联名称数组
     * @return self
     */
    public function with($relations): self
    {
        $Builders = [];
        if (is_array($relations)) {
            $this->with = $relations;
            foreach ($relations as $rela) {
                if (method_exists($this, $rela)) {
                    $Builders[$rela] = $this->$rela();
                }
            }
        } elseif (is_string($relations)) {
            $this->with[0] = $relations;
            if (method_exists($this, $relations)) {
                $Builders[$relations] = $this->$relations();
            }
        }
        $this->relationBuilders = $Builders;
       
        return $this;
    }

    /**
     * 插入记录（返回自增ID或影响行数）
     * @param array $data
     * @return int|false
     */
    public function insert($data)
    {
        $keys = array_keys($data);
        $placeholders = ':' . implode(', :', $keys);
        $sql = "INSERT INTO `{$this->table}` (`" . implode('`, `', $keys) . "`) VALUES ({$placeholders})";

        $this->query($sql, $data);
        return $this->db->lastInsertId();
    }

    /**
     * 更新记录（返回影响行数）
     * @param array $data 更新数据
     * @param array $conditions 条件（格式同find）
     * @return int 影响行数（0表示未更新）
     */
    public function update(array $data): int
    {
        try {
            // $this->queryParams['conditions'] = $conditions;
            $whereClause = $this->buildWhereClause();

            $setClause = $this->parseSetClause($data);
            $params = array_merge($setClause['params'], $whereClause['params']);

            $sql = "UPDATE `{$this->table}` SET {$setClause['sql']}{$whereClause['sql']}";
            $stmt = $this->query($sql, $params);

            // var_dump($stmt);
            return $stmt->rowCount();
        } finally {
            $this->resetQueryParams();
        }
    }

    /**
     * 解析SET子句（防止字段名注入）
     * @param array $data
     * @return array
     */
    protected function parseSetClause(array $data): array
    {
        $set = [];
        $params = [];
        foreach ($data as $field => $value) {
            if (!preg_match('/^[\w]+$/', $field)) {
                throw new InvalidArgumentException("字段名包含非法字符：{$field}");
            }
            $placeholder = ":update_{$field}";
            $params[$placeholder] = $value;
            $set[] = "`{$field}` = {$placeholder}";
        }

        return ['sql' => implode(', ', $set), 'params' => $params];
    }


    /**
     * 删除记录（返回影响行数）
     * @param array|int $conditions 主键ID 或 条件
     * @return int 影响行数（0表示未删除）
     */
    public function delete($conditions): int
    {
        try {
            if (is_numeric($conditions)) {
                $this->queryParams['conditions'] = [$this->pk => $conditions];
            } else {
                $this->queryParams['conditions'] = $conditions;
            }

            $whereClause = $this->buildWhereClause();
            $sql = "DELETE FROM `{$this->table}`{$whereClause['sql']}";
            $stmt = $this->query($sql, $whereClause['params']);

            return $stmt->rowCount();
        } finally {
            $this->resetQueryParams();
        }
    }

    /**
     * 通用预处理查询（返回PDOStatement）
     * @param string $sql
     * @param array $params
     * 
     */
    // @return PDOStatement
    public function query(string $sql, array $params = [])
    {
        try {
            $stmt = $this->db->prepare($sql);
            $stmt->execute($params);
            return $stmt;
        } catch (PDOException $e) {
            throw new RuntimeException("查询失败: {$e->getMessage()}, SQL: {$sql}, 参数: " . json_encode($params), 0, $e);
        }
    }

    /**
     * 处理添加和隐藏字段
     * @param array $data  查询记录
     */
    private function processResult(array $data)
    {
        if (empty($this->append) && empty($this->hidden) && empty($this->with)) {
            return $data;
        }

        array_walk($data, function (&$item) {
            foreach ($this->append as $v) {
                $item[$v] = $this->calculateAppendField($v, $item);
            }
            foreach ($this->hidden as $v) {
                $item = $this->calculateHiddenField($v, $item);
            }

        });

        if(!empty($this->with)){
            return $this->appendRelationJoins($data);
        }else{
            return $data;
        }

    }

    /**
     * 计算添加字段的值
     * @param string $field 字段名
     * @param array $item 当前记录
     * @return mixed
     */
    private function calculateAppendField(string $field, array $item)
    {
        $placeholders = explode('_', $field);
        $placeholder = '';
        foreach ($placeholders as $i) {
            $placeholder .= ucfirst($i);
        }
        $getter = 'get' . $placeholder . 'Attr';

        // 检查方法是否存在
        if (method_exists($this, $getter)) {
            return $this->$getter($item);
        }
    }

    /**
     * 计算隐藏字段的值
     * @param string $field 字段名
     * @param array $item 当前记录
     * @return mixed
     */
    private function calculateHiddenField(string $field, array $item)
    {
        unset($item[$field]);
        return $item;
    }

    /**
     * 处理联表字段
     * @param array $data  原查询记录
     * @return array
     */
    private function appendRelationJoins($data){
        if(empty($this->with) || empty($this->relationBuilders)){
            return $data;
        }

        foreach($this->relationBuilders as $key => $value){
            $foreignKeys = [];

            foreach($data as $item){
                $foreignKeys[] = $item[$value['foreignKey']];
            }

            $localKey = $value['localKey'] . ' in';
            $where = [
                $localKey => array_unique($foreignKeys)
            ];

            $joinData = $value['model']->where($where)->select();
            $reJoinData = [];
            foreach($joinData as $i){
                $reJoinData[$i['id']] = $i;
            }

            $this->relationBuilders[$key]['data'] = $reJoinData;

            foreach($data as &$v){
                $v[$key] = $reJoinData[$v[$value['foreignKey']]];
            }
        }
        return $data;
    }

    // /**
    //  * 开启事务
    //  */
    // public function startTrans(): void
    // {
    //     $this->db->beginTransaction();
    // }

    // /**
    //  * 提交事务
    //  */
    // public function commit(): void
    // {
    //     $this->db->commit();
    // }

    // /**
    //  * 回滚事务
    //  */
    // public function rollback(): void
    // {
    //     $this->db->rollBack();
    // }

    /**
     * 开启事务
     */
    public function startTrans(): void
    {
        if (self::$db) {
            if (self::$transactionLevel === 0) {
                self::$db->beginTransaction();
            }
            self::$transactionLevel++;
        }
    }

    /**
     * 提交事务
     */
    public function commit(): void
    {
        if (self::$db && self::$transactionLevel > 0) {
            self::$transactionLevel--;

            if (self::$transactionLevel === 0) {
                self::$db->commit();
            }
        }
    }

    /**
     * 回滚事务
     */
    public function rollback(): void
    {
        if (self::$db && self::$transactionLevel > 0) {
            // 即使有嵌套事务也要回滚整个事务
            self::$db->rollBack();
            self::$transactionLevel = 0;
        }
    }

    /**
     * 获取最后执行的SQL(调试用)
     * @return string
     */
    public function getLastSql(): string
    {
        return $this->db->query("SELECT LAST_INSERT_ID()")->fetchColumn() ?? '';
    }
}

?>